<?php
/**
 * Danbooru link grabber
 * Simple html link grabber from danbooru-like sites.
 * @author Izumi-kun
 */
class DanbooruLinkGrabber{

    /**
     * Path to dir (for cache dir and other)
     * @var string
     */
    public $workDir = '';

    /**
     * Complete url with
     * @var string
     */
    private $_url = '';

    /**
     * Just short name
     * @var string
     */
    private $_site = '';

    /**
     * Danbooru-stylised tags
     * @var string
     */
    private $_tags = '';

    /**
     * Caching can help get OVER9000 links, but request disk space
     * @var bool
     */
    public $noCache = false;

    /**
     * Additional curl options
     * @var array
     */
    private $_curlOpt = array();

    /**
     * How much iteration of requests
     * @var int
     */
    private $_multiplier = 1;

    /**
     * Hard limit per request
     * @var int
     */
    private $_limitPerReq = 10;

    /**
     * User limit
     * @var int
     */
    private $_limit = 100;

    /**
     * Sum of file sizes, bytes
     * @var int
     */
    private $_totalSize = 0;

    /**
     * True if counted _totalSize is NOT actual
     * @var bool
     */
    private $_totalSizeFail = false;

    /**
     * Total count of all links
     * @var int
     */
    private $_totalLinks = 0;

    /**
     * Plain text of links
     * @var string
     */
    private $_links = '';

    /**
     * Error messages
     * @var array
     */
    private $_errors = array();

    /**
     * Start position offset (like page)
     * @var int
     */
    private $_offset = 1;

    /**
     * User name for auth if requested
     * @var string
     */
    private $_user = '';

    /**
     * Generated password if requested
     * @var string
     */
    private $_pass = '';

    /**
     * Create request
     * @param  $site [konachan, chan.sankakucomplex, idol.sankakucomplex, behoimi, imouto, donmai]
     * @param  $tags
     * @param  $limit - total pictures i want (eg. 1000)
     * @param  $offset - page (eg. if i choose 1 then i want 1-1000 pictures, if - 2 then 1001-2000)
     * @param  $user
     * @param  $pass
     * @return void
     */
    public function initRequest($site, $tags, $limit, $offset, $user = '', $pass = ''){
        $this->_tags = preg_replace('/ /', '+', htmlspecialchars($tags));

        $this->_curlOpt         = array();
        $this->_links           = '';
        $this->_totalLinks      = 0;
        $this->_totalSize       = 0;
        $this->_totalSizeFail   = false;

        $_maxItems = 100;
        $_maxPages = 10;

        switch($site){
            case 'konachan':
                $this->_url = 'http://konachan.com/post/index.json';
                $_maxItems = 1000;
                break;
            case 'chan.sankakucomplex':
                $this->_url = 'http://chan.sankakucomplex.com/post/index.json';
                $_maxItems = 100;
                break;
            case 'idol.sankakucomplex':
                $this->_url = 'http://idol.sankakucomplex.com/post/index.json';
                $_maxItems = 100;
                break;
            case 'behoimi':
                $this->_url = 'http://behoimi.org/post/index.json';
                $_maxItems = 1000;
                break;
            case 'yande':
                $this->_url = 'https://yande.re/post/index.json';
                $_maxItems = 1000;
                break;
            case 'donmai':
                $this->_url = 'http://danbooru.donmai.us/post/index.json';
                $this->_user = $user;
                $this->_pass = sha1("choujin-steiner--$pass--");
                $_maxItems = 1000;
                break;
        }

        $this->_site        = $site;
        $this->_limit       = max(1, min($limit, $_maxItems * $_maxPages));
        $this->_limitPerReq = min($_maxItems, $this->_limit);
        $this->_multiplier  = min($_maxPages, ceil($this->_limit / $this->_limitPerReq));
        $this->_offset      = max(1, $offset);
    }

    /**
     * Get data from url
     * @param  int $page
     * @return string
     */
    private function _getContent($page = 1){
        if (!file_exists($this->workDir . '/cache')){
            mkdir($this->workDir . '/cache');
        }
        if ($this->_url === ''){
            $this->_errors[] = __METHOD__ . ': no url';
            return false;
        }
        if (!file_exists($this->workDir . '/cache/' . $this->_site)){
            mkdir($this->workDir . '/cache/' . $this->_site);
        }
        $_fullUrl = "$this->_url?limit=$this->_limitPerReq&tags=$this->_tags&page=$page";
        if ($this->_user){
            $_fullUrl .= "&login=$this->_user&password_hash=$this->_pass";
        }
        $_fileName = sha1($_fullUrl);
        $_fileFullName = $this->workDir . '/cache/' . $this->_site . '/' . $_fileName;
        $_eTagFileFullName = $_fileFullName . '.etag';
        if ($this->noCache || !file_exists($_fileFullName) || !file_exists($_eTagFileFullName)){
            $_headers = $this->_getHeaders($_fullUrl);
            $_content = $this->_getFileByUrl($_fullUrl);
            if (!$this->noCache){
                file_put_contents($_fileFullName, $_content);
                file_put_contents($_eTagFileFullName, $_headers['etag']);
            }
            return $_content;
        }
        elseif (file_exists($_fileFullName) && file_exists($_eTagFileFullName)){
            $_headers = $this->_getHeaders($_fullUrl);
            if ($_headers['etag'] == file_get_contents($_eTagFileFullName)){
                return file_get_contents($_fileFullName);
            }
            else {
                $_content = $this->_getFileByUrl($_fullUrl);
                file_put_contents($_fileFullName, $_content);
                file_put_contents($_eTagFileFullName, $_headers['etag']);
                return $_content;
            }
        }
        $this->_errors[] = __METHOD__ . ': cant get content';
        return false;
    }

    /**
     * Downloading file
     * @param  $url
     * @return mixed
     */
    private function _getFileByUrl($url){
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_USERAGENT, sha1(rand()).'/2.0');

        curl_setopt_array($ch, $this->_curlOpt);
        $_data = curl_exec($ch);
        curl_close($ch);
        return $_data;
    }

    /**
     * Return entity tag
     * @param  $url
     * @return mixed
     */
    private function _getHeaders($url){
        $ch = curl_init();

        //requered options
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_NOBODY, true);
        curl_setopt($ch, CURLOPT_HEADER, true);
        curl_setopt($ch, CURLOPT_USERAGENT, sha1(rand()).'/2.0');

        //additional options
        curl_setopt_array($ch, $this->_curlOpt);

        $_data = curl_exec($ch);

        curl_close($ch);
        if (!$_data){
            $this->_errors[] = __METHOD__ . ': connection failed.';
            return false;
        }
        if (preg_match('/ETag: (\S*)/', $_data, $_etag)) {
            return array(
                'etag' => $_etag[1]
            );
        }
        else {
            $_arr = preg_split('/[\r\n]+/', $_data);
            $this->_errors[] = __METHOD__ . ': ' . $this->_site . ' have butthurt' . (isset($_arr[0]) ? ': ' . $_arr[0] : '');
            return false;
        }
    }

    /**
     * Generate links
     * @return bool (true - links grabbed successfully)
     */
    public function processRequest(){
        if (!$this->_url){
            $this->_errors[] = __METHOD__ . ': empty "site" property';
            return false;
        }
        if (!$this->_tags){
            $this->_errors[] = __METHOD__ . ': no tags';
            return false;
        }
        $_cnt = 0;
        $_mainData = '';
        $_ignore = ($this->_offset - 1) * (($this->_limit + $this->_limitPerReq) % $this->_limitPerReq);
        $_page = $_startPage = ($this->_offset - 1) * max(1, $this->_multiplier) + 1;
        $this->_totalLinks = $_totalLinks = 0;
        while ($_page <= $this->_multiplier + $_startPage && $_cnt < $this->_limit){
            $_array = json_decode($this->_getContent($_page), true);
            $_page++;
            if (is_array($_array)){
                $_t_cnt = 0;
                if ($_ignore){
                    $_array = array_slice($_array, $_ignore);
                    $_ignore = 0;
                }
                foreach ($_array as $value){
                    $this->_totalSize += $value['file_size'];
                    if (empty($value['file_size'])){
                        $this->_totalSizeFail = true;
                    }
                    $_mainData .= $value['file_url']."\n";
                    $_totalLinks++;
                    $_cnt++;
                    $_t_cnt++;
                    if ($_cnt >= $this->_limit){
                        break;
                    }
                }
                if ($_t_cnt < $this->_limitPerReq){
                    break;
                }
            }
            else {
                $this->_errors[] = __METHOD__ . ': json_decode return FALSE';
                return false;
            }
        }
        
        //correct multiplier value by real used pages
        $this->_multiplier = $_page - $_startPage;

        if (!$_mainData){
            $this->_errors[] = __METHOD__ . ': No any entries for these params';
            return false;
        }

        //save links count
        $this->_totalLinks = $_totalLinks;

        //save links
        $this->_links = $_mainData;
        return true;
    }

    /**
     * Just get link
     * @return string
     */
    public function getLinks(){
        return $this->_links;
    }

    /**
     * Total size of files
     * @return int
     */
    private function _getTotalSize(){
        if ($this->_totalSizeFail){
            return 0;
        }
        else {
            return $this->_totalSize;
        }
    }

    /**
     * Some info
     * @return array ['totalLinks', 'pagesUsed', 'totalSize', 'site']
     */
    public function getRequestInfo(){
        return array(
            'site'          => $this->_site,
            'totalLinks'    => $this->_totalLinks,
            'pagesUsed'     => $this->_multiplier,
            'totalSize'     => $this->_getTotalSize()
        );
    }

    /**
     * Return all errors
     * @return string
     */
    public function getDebugMsg(){
        $_msgs  = "DEBUG INFO\n";
        $_msgs .= "Site:       " . $this->_site . "\n";
        $_msgs .= "Tags:       " . $this->_tags . "\n";
        $_msgs .= "Limit:      " . $this->_limit . "\n";
        $_msgs .= "Offset:     " . $this->_offset . "\n";
        $_msgs .= "Multiplier: " . $this->_multiplier . "\n";
        $_msgs .= "Errors:     " . sizeof($this->_errors) . "\n";
        foreach ($this->_errors as $msg){
            $_msgs .= "    " . $msg . "\n";
        }
        $_msgs .= "END";

        return $_msgs;
    }

}
